//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
//==========================================================================
// Copyright (c) 2000-2004,  Elastos, Inc.  All Rights Reserved.
//**author:hanxu
//**y/m/d:2005/5/26
//==========================================================================

#include <ddk.h>
#include <stdlib.h>
#include "vga.h"
#include "smi712.h"
#include "pci.h"
#include "font_8x16.h"

EXTERN_C void PrintComPort(const char *szMessage);

extern unsigned int _pci_conf_read(unsigned int tag, int reg);

static uint32_t FrameBufAddr = 0xbb000000;
static uint32_t VgaIoAddr = 0x00000000;

VGAModeInit VGAModes[KNOWN_VGAMODES] ={ //for sm712
	{// 800 x 600  16Bpp  60Hz
		800, 600, 16, 60,
		//  Init_MISC
		0x2B,
		{   //  Init_SR0_SR4
			0x03, 0x01, 0x0F, 0x03, 0x0E,
		},
		{   //Init_SR10_SR24
			0xFF, 0xBE, 0xEE, 0xFF, 0x00, 0x0E, 0x17, 0x2C,
			0x99, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
			0xC4, 0x30, 0x02, 0x01, 0x01,
		},
		{   //Init_SR30_SR75
			0x24, 0x03, 0x20, 0x09, 0xC0, 0x24, 0x24, 0x24,
			0x24, 0x24, 0x24, 0x24, 0x00, 0x00, 0x03, 0xFF,
			0x00, 0xFC, 0x00, 0x00, 0x20, 0x38, 0x00, 0xFC,
			0x20, 0x0C, 0x44, 0x20, 0x00, 0x24, 0x24, 0x24,
			0x04, 0x48, 0x83, 0x63, 0x68, 0x72, 0x57, 0x58,
			0x04, 0x55, 0x59, 0x24, 0x24, 0x00, 0x00, 0x24,
			0x01, 0x80, 0x7A, 0x1A, 0x1A, 0x00, 0x00, 0x00,
			0x50, 0x03, 0x74, 0x14, 0x1C, 0x85, 0x35, 0x13,
			0x02, 0x45, 0x30, 0x35, 0x40, 0x20,
		},
		{   //Init_SR80_SR93
			0x00, 0x00, 0x00, 0x6F, 0x7F, 0x7F, 0xFF, 0x24,
			0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x24, 0x24,
			0x00, 0x00, 0x00, 0x00,
		},
		{   //Init_SRA0_SRAF
			0x00, 0xFF, 0xBF, 0xFF, 0xFF, 0xED, 0xED, 0xED,
			0x7B, 0xFF, 0xFF, 0xFF, 0xBF, 0xEF, 0xBF, 0xDF,
		},
		{   // Init_GR00_GR08
			0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F,
			0xFF,
		},
		{   //Init_AR00_AR14
			0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
			0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
			0x41, 0x00, 0x0F, 0x00, 0x00,
		},
		{   // Init_CR00_CR18
			0x7F, 0x63, 0x63, 0x00, 0x68, 0x18, 0x72, 0xF0,
			0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x58, 0x0C, 0x57, 0x64, 0x40, 0x57, 0x00, 0xE3,
			0xFF,
 		},
		{   //Init_CR30_CR4D
			0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x03, 0x20,
			0x00, 0x00, 0x00, 0x40, 0x00, 0xE7, 0xBF, 0xFD,
			0x7F, 0x63, 0x00, 0x69, 0x18, 0x72, 0x57, 0x00,
			0x58, 0x0C, 0xE0, 0x20, 0x63, 0x57,
		},
		{  //Init_CR90_CRA7
			0x56, 0x4B, 0x5E, 0x55, 0x86, 0x9D, 0x8E, 0xAA,
			0xDB, 0x2A, 0xDF, 0x33, 0x00, 0x00, 0x18, 0x00,
			0x20, 0x1F, 0x1A, 0x19, 0x0F, 0x0F, 0x0F, 0x00,
		},
	},
};

vgaInitParam g_vgaInitParam = {
    800,   // screen width
    600,   // screen height
    16,    // Bpp
    60,    // screen brush rate
    0,     // cursor location(unit: pixel)
    0,     // cursor location(unit: text char)
    8,     // text char width
    16,    // text char height
    0xffff // draw color
};

vgaInfo g_vgaInfo;

vgaInfo* vga_device_probe(void)
{
    struct pci_dev * vga_dev;

    kprintf("starting vga device probe...\n");

    if (!(vga_dev = (struct pci_dev * ) pci_find_class (PCI_CLASS_DISPLAY_VGA << 8)))
    {
	    kprintf("no vga device being found!\n");
	    return NULL;
	}

    kprintf("find vga device, verify the vga type....\n ");

    g_vgaInfo.vgaregs.chipID = vga_dev->device;

    if(vga_dev->device == smi712_device_id || vga_dev->vendor == smi712_vendor_id)
    {
        FrameBufAddr = _pci_conf_read(vga_dev->pmontag,0x10) | 0xb0000000;
        VgaIoAddr = _pci_conf_read(vga_dev->pmontag,0x18);

        kprintf("The vga device is smi712\nThe frame buffer is 0x%x\nThe IO address is 0x%x\n",FrameBufAddr,VgaIoAddr);
        g_vgaInfo.frameBuf = FrameBufAddr;
        g_vgaInfo.vgaregs.puLFB = SMILFB = (uint8_t *)g_vgaInfo.frameBuf;
        g_vgaInfo.ioBuf = VgaIoAddr;
        g_vgaInfo.vgaregs.puMMIO = SMIRegs = (uint8_t *)(SMILFB + 0x00700000);
        g_vgaInfo.vgaregs.puDPR = (uint8_t *)(SMILFB + 0x00408000);
        g_vgaInfo.vgaregs.puVPR = (uint8_t *)(SMILFB + 0x0040c000);
        g_vgaInfo.vgaregs.puCPR = (uint8_t *)(SMILFB + 0x0040e000);


        return &g_vgaInfo;
    }

    return NULL;
}

bool smi_set_timing(vgaInitParam *pvParameter, vgaInfo *pInfo)
{
    int i=0;
    int j=0;
    unsigned int  uiScreenStride;

    for(j=0; j<KNOWN_VGAMODES; j++){
	    if(VGAModes[j].uSizeX == pvParameter->ScrWidth &&
	       VGAModes[j].uSizeY == pvParameter->ScrHeight &&
	       VGAModes[j].uBpp == pvParameter->Bpp  &&
	       VGAModes[j].uHz == pvParameter->Hz)
	    {
	        smi_mmiowb(0x0,0x3c6);
		    smi_seqw(0,0x1);
		    smi_mmiowb(VGAModes[j].Init_MISC,0x3c2);

		    for (i=0;i<SIZE_SR00_SR04;i++)  // init SEQ00~SEQ04
		    {
		        smi_seqw(i,VGAModes[j].Init_SR00_SR04[i]);
		    }

		    for (i=0;i<SIZE_SR10_SR24;i++)  // init SCR10 ~ SCR1F, PDR20 ~PDR24
		    {
		        smi_seqw(i+0x10,VGAModes[j].Init_SR10_SR24[i]);
		    }

		    for (i=0;i<SIZE_SR30_SR75;i++)	// init FPR30~FPR5A, MCR60~MCR62, CCR65~CCR6F, GPR70~GPR75
		    {
		        if (((i+0x30) != 0x62) && ((i+0x30) != 0x6a) && ((i+0x30) != 0x6b))
		            smi_seqw(i+0x30,VGAModes[j].Init_SR30_SR75[i]);
		    }

		    for (i=0;i<SIZE_SR80_SR93;i++)	// init PHR80~PHR81, POP82~POP86, POP90~POP93, HCR88~HCR8D
			{
		        smi_seqw(i+0x80,VGAModes[j].Init_SR80_SR93[i]);
		    }

		    for (i=0;i<SIZE_SRA0_SRAF;i++)	// init FPRA0~FPRAF
		    {
		        smi_seqw(i+0xa0,VGAModes[j].Init_SRA0_SRAF[i]);
		    }

		    for (i=0;i<SIZE_GR00_GR08;i++)	// init Graphic register GRX00~GRX08
		    {
		        smi_grphw(i,VGAModes[j].Init_GR00_GR08[i]);
		    }

		    for (i=0;i<SIZE_AR00_AR14;i++)	// init Attribute register AR00 - AR14
		    {
				smi_attrw(i,VGAModes[j].Init_AR00_AR14[i]);//	Select color display text attributes
														  //	Select graphics control mode(not text control mode)
			}

			for (i=0;i<SIZE_CR00_CR18;i++)	// init CRTC register CR00 - CR18
		    {
		        smi_crtcw(i,VGAModes[j].Init_CR00_CR18[i]);
		    }

		    for (i=0;i<SIZE_CR30_CR4D;i++)	// init CRTC register CRT30~CRT39, CRT3B,  SVR40~SVR4D
		    {
		        smi_crtcw(i+0x30,VGAModes[j].Init_CR30_CR4D[i]);
		    }

		    for (i=0;i<SIZE_CR90_CRA7;i++)	// init CRTC register CR90 - CRA7
		    {
		        smi_crtcw(i+0x90,VGAModes[j].Init_CR90_CRA7[i]);
		    }

		    //------------------------------------------
		    smi_mmiowb(0x67,0x3c2);//Enable Video RAM access from CPU
								   //Select for color mode.

		    // set VPR registers
			writel(0x0, (uint32_t)pInfo->vgaregs.puVPR+0x0C);
			writel(0x0, (uint32_t)pInfo->vgaregs.puVPR+0x40);

			uiScreenStride = (pvParameter->ScrWidth * pvParameter->Bpp) / 64; //800*16/64=200

		    //select Graphics Data Format (GDF) :16-bit 5-6-5 RGB
			writel(0x00020000, (uint32_t)pInfo->vgaregs.puVPR+0x0);

			writel((unsigned int)(((uiScreenStride + 2) << 16) | uiScreenStride), (uint32_t)pInfo->vgaregs.puVPR+0x10);

			return true;
	     }

    }

    kprintf("vga mode not match!\n ");
	return false;
}


//We need to wake up the LynxEM+, and make sure its in linear memory mode.
void smi_init_hw(void)
{
    outb(0x18, 0x3c4);
    outb(0x11, 0x3c5);
}

ECode Video::m_vga_init(vgaInitParam *pvParameter, vgaInfo *pInfo)
{
    kprintf("entering smi712 initiation......\n");
    assert(pInfo->vgaregs.chipID == smi712_device_id && "vga chipID unmatched!\n");

    smi_init_hw();

    smi_seqw(0x62,0x7A);
    smi_seqw(0x6a,0x0c);
    smi_seqw(0x6b,0x02);

    smi_seqw(0x6a,0x12);
    smi_seqw(0x6b,0x02);  // MCLK = 85.9Mhz
    smi_seqw(0x62,0x3e);// Internal logic will be running 1/2X MCLK

    kprintf("ready to enter smi_set_timing()!\n");

     if (!smi_set_timing(pvParameter, pInfo))
	{
	    kprintf("setup vga config fault!\n");
	    return E_FAIL;
    }

	kprintf("SMIInit complete.\n");

	return NOERROR;
}

//
// The vga driver's classfactory
//
EXTERN IDeviceDriver * CDECL CreateVGA(uint_t uDeviceNo, void *pvParameter)
{
    Video *pDriver = new Video;
    if (NULL == pDriver) {
        assert(0 && "There is not enough memeory!");
        return NULL;
    }

    vgaInfo *pInfo;
    vgaInitParam *vgaParam = (vgaInitParam *)pvParameter;
    pInfo = vga_device_probe();
    if (NULL == pInfo){
        kprintf("vga probe Failed!\n");
        return NULL;
    }
    pDriver->m_uFrameBufBase = pInfo->frameBuf;
    pDriver->m_uIoBufBase = pInfo->ioBuf;

    pDriver->m_uScrWidth = vgaParam->ScrWidth;
    pDriver->m_uScrHeight = vgaParam->ScrHeight;
    pDriver->m_uScrBpp = vgaParam->Bpp;
    pDriver->m_uScrHz = vgaParam->Hz;
    pDriver->m_uCursorLocation = vgaParam->CursorLocation;
    pDriver->m_uTxtCursorLocation = vgaParam->TxtCursorLocation;
    pDriver->m_uCharWidth = vgaParam->CharWidth;
    pDriver->m_uCharHeight = vgaParam->CharHeight;
    pDriver->m_u16DrawColor = vgaParam->DrawColor;

    pDriver->m_uScrTxtCols =
    	    pDriver->m_uScrWidth / pDriver->m_uCharWidth;
    pDriver->m_uScrTxtRows =
    	    pDriver->m_uScrHeight / pDriver->m_uCharHeight;

    ECode ec;
    ec = pDriver->m_vga_init((vgaInitParam *)pvParameter, pInfo);

    if(FAILED(ec))
    {
        assert(0 && "vga initialise fault!");
        return NULL;
    }

    kprintf("create vga OK!\n");

    pDriver->m_bGDIMODE = false;

    // clean screen
    for(uint32_t i = 0;
        i < pDriver->m_uScrWidth * pDriver->m_uScrHeight * pDriver->m_uScrBpp / 8; //800*600*16/2
            i++)
    *(uint8_t *)(pDriver->m_uFrameBufBase + i) = 0;

    pDriver->AddRef();

    return pDriver;
}

ECode Video::Read(
        /* [in] */ UInt64 u64Offset,
        /* [in] */ UInt32 bytesToRead,
        /* [out] */ MemoryBuf * pBuffer,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode Video::Write(
        /* [in] */ UInt64 u64Offset, // start address
        /* [in] */ const MemoryBuf & buffer, // print context
        /* [out] */ UInt32 * pBytesWritten, // printed
        /* [out] */ IEvent * * ppCompletionEvent)
{
    wchar_t *pwcString = (wchar_t *)buffer.GetPayload();
    uint32_t uLength = buffer.GetUsed()/2;

    if(m_bGDIMODE) return NOERROR;

    uint32_t buffer;
    uint32_t txtBuf = m_uScrTxtCols * m_uScrTxtRows;

    if (0 == uLength || (ppCompletionEvent && *ppCompletionEvent)) {
        return E_INVALID_ARGUMENT;
    }

    // If appointment start address is bigger than the screen buffer, the
    // Cursor immovability.
    if (u64Offset < m_uScrTxtCols * m_uScrTxtRows) {
        m_uTxtCursorLocation = (uint32_t)u64Offset;
    }
    else {
        u64Offset = m_uTxtCursorLocation;
    }

    buffer = m_uFrameBufBase +
              (m_uTxtCursorLocation / m_uScrTxtCols) * m_uCharHeight * m_uScrWidth *2 +
               (m_uTxtCursorLocation % m_uScrTxtCols) * m_uCharWidth *2;

    // Output the string
    for(uint32_t i = 0; (i < uLength) && (m_uTxtCursorLocation < txtBuf); i++) {

        DrawChar((char)pwcString[i], buffer, m_u16DrawColor);
        m_uTxtCursorLocation++;
        buffer += m_uCharWidth*2;

        if((!(m_uTxtCursorLocation%m_uScrTxtCols))&&(m_uTxtCursorLocation > 0))
            buffer += m_uScrTxtCols * m_uCharHeight * m_uCharWidth*2 - m_uScrTxtCols * m_uCharWidth*2;
    }

    if (pBytesWritten) {
        *pBytesWritten = (m_uTxtCursorLocation - (uint32_t)u64Offset);
        assert(*pBytesWritten >= 0);
    }

    return NOERROR;
}

ECode Video::Control(
        /* [in] */ Handle32 nControlCode,
        /* [in] */ const MemoryBuf & inBuffer,
        /* [out] */ MemoryBuf * pOutBuffer,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    ECode ec;

    if ((nControlCode < 0) || (nControlCode > 9)
        || (ppCompletionEvent && *ppCompletionEvent)) {
        return E_INVALID_ARGUMENT;
    }

    switch(nControlCode)
    {
        // Set the cursor's position
        case 0:{
            if (pOutBuffer == NULL || !pOutBuffer->GetCapacity()) {
                return E_INVALID_ARGUMENT;
            }

            uint32_t uPosition = *(uint32_t *)inBuffer.GetPayload();

            if (uPosition >= m_uScrTxtCols * m_uScrTxtRows) {
                return E_INVALID_ARGUMENT;
            }
//            SetCursorLocation(uPosition);
            m_uTxtCursorLocation = uPosition;

            break;
        }

        // Get the cursor's position
        case 1:{
            if (inBuffer.IsNullOrEmpty() || pOutBuffer == NULL ||
                ((uint32_t)pOutBuffer->GetCapacity() < sizeof(m_uTxtCursorLocation)) {
                return E_INVALID_ARGUMENT;
            }

            *(uint32_t *)pOutBuffer->GetPayload() = m_uTxtCursorLocation;

            pOutBuffer->SetUsed(sizeof(m_uTxtCursorLocation));

            break;
        }

        // Get the screen column
        case 2:{
            if (inBuffer.IsNullOrEmpty() || pOutBuffer == NULL ||
                ((uint32_t)pOutBuffer->GetCapacity() < sizeof(m_uScrTxtCols)) {
                return E_INVALID_ARGUMENT;
            }

            *(uint32_t *)pOutBuffer->GetPayload() = m_uScrTxtCols;

            pOutBuffer->SetUsed(sizeof(m_uScrTxtCols));

            break;
        }

        // Get the screen row
        case 3:{
            if (inBuffer.IsNullOrEmpty() || pOutBuffer == NULL ||
                ((uint32_t)pOutBuffer->GetCapacity() < sizeof(m_uScrTxtRows)) {
                return E_INVALID_ARGUMENT;
            }

            *(uint32_t *)pOutBuffer->GetPayload() = m_uScrTxtRows;

            pOutBuffer->SetUsed(sizeof(m_uScrTxtRows));

            break;
        }

        // Set the cursor shape
        case 4:{
            if (pOutBuffer == NULL || !pOutBuffer->GetCapacity()) {
                return E_INVALID_ARGUMENT;
            }
//            SetCursorShape(*(bool_t *)(wchar_t *)inBuffer);
           break;
        }

        // set draw color
        case 5:{
            if (pOutBuffer == NULL || !pOutBuffer->GetCapacity()) {
                return E_INVALID_ARGUMENT;
            }

            uint16_t uColor = *(uint16_t *)inBuffer.GetPayload();

            m_u16DrawColor = uColor;

            break;
        }

        // get draw color
        case 6:{
            if (inBuffer.IsNullOrEmpty() || pOutBuffer == NULL ||
                ((uint32_t)pOutBuffer->GetCapacity() < sizeof(m_u16DrawColor)) {
                return E_INVALID_ARGUMENT;
            }

            *(uint32_t *)pOutBuffer->GetPayload() = m_u16DrawColor;

            pOutBuffer->SetUsed(sizeof(m_u16DrawColor));

            break;
        }

        // clean screen
        case 7:{
            for(uint32_t i = 0;
                    i < m_uScrWidth * m_uScrHeight * m_uScrBpp / 8; //800*600*16/2
                        i++)
                *(uint8_t *)(m_uFrameBufBase + i) = 0;

            m_uTxtCursorLocation = 0;
            break;
        }

        //IO_Remap
        case 8:{
            ec = DzIoRemap(
        		0,
//        		PAGE_SIZE,
        		RoundUp2(m_uScrWidth * m_uScrHeight * m_uScrBpp / 8, PAGE_SIZE),
        		m_uFrameBufBase,
        		0,
        		MemoryProtection_RWIO,
        		(Address *)&m_uVirtAddr);

       	    if (FAILED(ec)) {
                m_uVirtAddr = 0;
                return ec;
            }

            assert(0 != m_uVirtAddr);

	        pOutBuffer->Copy(&m_uVirtAddr,4);

            break;
        }

        // set vga mode
        case 9:{
            uint8_t uMode = *(uint8_t *)inBuffer.GetPayload();

            m_bGDIMODE = (VGATXTMODE == uMode)? false:true;
//            pOutBuffer->Copy(&m_bGDIMODE,4);

            break;
        }

        default:
        return E_INVALID_OPTIONS;
    }

    return NOERROR;
}

ECode Video::WritePixel(
        /* [in] */ UInt64 u64Offset, // start address
        /* [in] */ const MemoryBuf & buffer, // print context
        /* [out] */ UInt32 * pBytesWritten, // printed
        /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

EXTERN void DrawChar(char chr, uint32_t buffer, uint16_t draw_color)
{
    int char_row,char_col;
    char c = chr;

    uint16_t *base = (uint16_t *)buffer;
    uint8_t line;

    if((c > 0x80)||(c < 0x20)) c=0x20;

    for(char_row = 0; char_row < CHAR_HEIGHT; char_row++) {
    	line = FONT[c * CHAR_HEIGHT + char_row];

        // be careful: j should be decrement order, otherwise it will print a mirror char.
    	for(char_col = CHAR_WIDTH; char_col > 0 ; char_col--) {

    	    if (line & 0x1) {
    		    base[char_col] = draw_color;
    		}
    		else{
    		    base[char_col] = 0;
    		}

    	    line = line >> 1;
    	}
    	base += g_vgaInitParam.ScrWidth;
    }
}
